#include "math.h"

#include "boost/foreach.hpp"

#include "ParticleManager.h"
#include "Configuration.h"

ParticleManager::ParticleManager
        (
            const float _height,
            const float _width,
            const float _verticalCount,
            const float _horizontalCount,
            const float _mass,
            const float _radius
        )
{
    //use the given params to create particles and position them to fill in the 2d space
    float xStep = _width / _horizontalCount;
    float zStep = _height / _verticalCount;

    float xStartPos = _width / 2;
    float zStartPos = _height / 2;

    for (int z = 0; z < _verticalCount; z++)
    {
        for (int x = 0; x < _horizontalCount; x++)
        {
            //determine id of next particle to create
            int id = (z * _verticalCount) + x;

            //create a particle
            Particle* p = new Particle(id, _mass, _radius);

            //set position of particle
            p->setPosition(ngl::Vector(xStartPos + (x * xStep), 0, zStartPos + (z * zStep)));
        }
    }

    //connect particles with their neighbours for structural, shear and flexion
}

void ParticleManager::evaluateNetForces()
{
    //calculate the net force on each particle wrt its neighbours
    BOOST_FOREACH(Particle* currentParticle, *m_particleList)
    {
        //get neighbour list for current particle
        std::vector<Particle*>* neighbourList = currentParticle->getNeighbourList();

        //evaluate net force of neighbours on current particle

        //clear net force
        currentParticle->clearNetForce();

        //adds in gravity
        currentParticle->addToNetForce(new ngl::Vector(0, currentParticle->getMass() * Configuration::s_accelerationOfFreeFall, 0));

        //loop for each neighbour => adds spring and damping forces
        BOOST_FOREACH(Particle* neighbour, *neighbourList)
        {
            //adds in spring force

            //adds in damping force
        }

        //adds in wind interaction

        //clamp force within a force range
    }
}

void ParticleManager::evaluateEularExplicit()
{
    //evaluate net forces on each particle
    evaluateNetForces();

    //loop over the list of particles and integrate them
    BOOST_FOREACH(Particle* currentParticle, *m_particleList)
    {
        //apply eular forward explicit integration to get new positions of particles
        //use Newton's 2nd law F = ma => get accelation a = F / m
        //use a = d(v) / dt => integrate to get velocity v
        //use v = d(s) / dt => integrate to get displacement s


    }


}

void ParticleManager::evaluateRK2()
{
    //evaluate net forces on each particle
    evaluateNetForces();

    //apply RK2 integration to get new positions of particles
    //TODO
}

void ParticleManager::evaluateRK4()
{
    //evaluate net forces on each particle
    evaluateNetForces();

    //apply RK4 integration to get new positions of particles
    //TODO
}

void ParticleManager::evaluateVerlet()
{
    //evaluate net forces on each particle
    evaluateNetForces();

    //apply Verlet integration to get new positions of particles
    //TODO
}


void ParticleManager::loadGeometry()
{
    //create vbo for obstacle
    ngl::VBOPrimitives* prim = ngl::VBOPrimitives::instance();

    prim->createVBOSphere("sphere", 1, 16);

}

void ParticleManager::draw
                    (
                        ngl::TransformStack _txStack,
                        ShaderManager* io_sman,
                        const std::string _shader
                    )
{
    ngl::VBOPrimitives* prim=ngl::VBOPrimitives::instance();

    //DEBUG FOR NOW -------------------------
    bool m_drawWireframe = true;

    //loop through all particles
    BOOST_FOREACH(Particle* particle, *m_particleList)
    {
        //draw sphere

        if (m_drawWireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        _txStack.pushTransform();
        {
            _txStack.setPosition(particle->getPosition().m_x, particle->getPosition().m_y, particle->getPosition().m_z);

            _txStack.setScale(particle->getRadius(), particle->getRadius(), particle->getRadius());

            //update transform and color to shader
            io_sman->updateModelColor(_shader, _txStack.getCurrAndGlobal().getMatrix(), ngl::Colour(0, 0, 1));

            //draw sphere
            prim->draw("sphere");
        }
        _txStack.popTransform();
        if (m_drawWireframe) glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    }

}

